package com.ilikesoup.instant.enums.basic;

import org.springframework.util.ConcurrentReferenceHashMap;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 涉及到前端下拉展示的枚举，可以实现该接口
 *
 * 扩展了 CodeEnum 接口，引用了 CodeEnum 中提供的工具方法
 *  {@link SelectEnum#getByCode} 通过code获取枚举项，可以用于反显
 *  {@link SelectEnum#contains}  通过code判断枚举项是否存在，可以用于接收参数的合法性校验
 *
 * 新增下拉用的描述项，提供了下拉框和反显会使用的工具方法
 *  {@link SelectEnum#list} 和 {@link SelectEnum#asList} 可以用于获取前端需要的下拉列表
 *  {@link SelectEnum#getDescByCode} 可以用于后台反显枚举描述
 *
 * 可以通过扩展 SelectVo 类并重写 {@link SelectEnum#getSelectVo} 来扩展下拉列表项的属性
 * 可以通过在枚举中重写 {@link SelectEnum#getSelectVo} 使其返回 null 来排除该项的前端展示
 *
 * JDK-8141508:
 *  <T extends Enum<T> & MapEnum<K>, K>
 *  创建流时先将其标记为需要使用的类型 Arrays.<SelectEnum<K>>stream(...
 *
 * @author  Soup
 * @param <K>   枚举项code的类型
 */
public interface SelectEnum<K> extends CodeEnum<K> {

    /**
     * @return 枚举项的描述，必须唯一（否则，下拉展示让人混淆）
     */
    String getDesc();

    /**
     * @return 由枚举项转换成的下拉项，可以通过扩展SelectVo类并重写这个方法来扩展下拉参数
     */
    default SelectVo getSelectVo() {
        return new SelectVo(Objects.toString(getCode()), getDesc());
    }

    /**
     * 将枚举项转换为下拉列表
     *
     * @param selectEnumClass 枚举类
     * @param <T>             枚举类型
     * @return 不要再返回的List上编辑
     */
    static <T extends Enum<T> & SelectEnum<K>, K> List<SelectVo> list(Class<T> selectEnumClass) {
        return SelectEnumCache.getEnumSelect(selectEnumClass);
    }

    /**
     * 将枚举项转换到同一个 map 中
     * code:desc
     *
     * @param selectEnumClass   需要转换的枚举
     * @param <T>   枚举类型
     * @param <K>   编码类型
     * @return  以 code 为键，以 desc 为值构成键值对的 Map
     */
    static <T extends SelectEnum<K>, K> Map<K, String> map(Class<T> selectEnumClass) {
        return Arrays.<SelectEnum<K>>stream(selectEnumClass.getEnumConstants())
                .collect(Collectors.toMap(SelectEnum::getCode, SelectEnum::getDesc, (a, b) -> a, LinkedHashMap::new));
    }

    /**
     * 将给定的枚举项转换为下拉列表
     *
     * @param selectEnums 枚举项数组
     * @param <T>         枚举类型
     * @return 枚举项生成的下拉项列表
     */
    @SafeVarargs
    static <T extends SelectEnum<?>> List<SelectVo> asList(T... selectEnums) {
        return Arrays.<SelectEnum<?>>stream(Objects.requireNonNull(selectEnums))
                .map(SelectEnum::getSelectVo).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 通过枚举项编码获取枚举项描述
     *
     * @param code            枚举项编码
     * @param selectEnumClass 枚举类
     * @param <T>             枚举类型
     * @param <K>             编码类型
     * @return 对应编码的枚举项描述
     */
    static <T extends Enum<T> & SelectEnum<K>, K> String getDescByCode(K code, Class<T> selectEnumClass) {
        T constant = CodeEnum.getByCode(code, selectEnumClass);
        return constant == null ? null : constant.getDesc();
    }

    /**
     * 通过枚举项编码获取枚举项
     *
     * @param code            枚举项编码
     * @param selectEnumClass 枚举类
     * @param <T>             枚举类型
     * @param <K>             编码类型
     * @return 对应编码的枚举项
     */
    static <T extends Enum<T> & SelectEnum<K>, K> T getByCode(K code, Class<T> selectEnumClass) {
        return CodeEnum.getByCode(code, selectEnumClass);
    }

    /**
     * 通过枚举项编码获取枚举项
     *
     * @param code            枚举项编码
     * @param selectEnumClass 枚举类
     * @param def             默认值，如果找不到匹配的项，就返回该默认值
     * @param <T>             枚举类型
     * @param <K>             编码类型
     * @return 对应编码的枚举项，如果找不到匹配的项，就返回该默认值
     */
    static <T extends Enum<T> & SelectEnum<K>, K> T getByCode(K code, Class<T> selectEnumClass, T def) {
        return CodeEnum.getByCode(code, selectEnumClass, def);
    }

    /**
     * 判断编码在枚举中是否存在
     *
     * @param code            枚举项编码
     * @param selectEnumClass 枚举类
     * @param <T>             枚举类型
     * @param <K>             编码类型
     * @return 存在返回true, 不存在返回false
     */
    static <T extends Enum<T> & SelectEnum<K>, K> boolean contains(K code, Class<T> selectEnumClass) {
        return CodeEnum.contains(code, selectEnumClass);
    }

    /**
     * 将普通枚举项转换为下拉列表
     *
     * @param enumClass  枚举类
     * @param codeGetter 编码Getter
     * @param descGetter 描述Getter
     * @param <T>        枚举类型
     * @param <K>        code类型
     * @return 枚举项生成的下拉项列表
     */
    static <T extends Enum<T>, K> List<SelectVo> list(Class<T> enumClass, Function<T, K> codeGetter, Function<T, String> descGetter) {
        return Arrays.stream(enumClass.getEnumConstants())
                .map(o -> new SelectVo(String.valueOf(codeGetter.apply(o)), descGetter.apply(o))).collect(Collectors.toList());
    }

    /**
     * 将给定的普通枚举项转换为下拉列表
     *
     * @param codeGetter  编码Getter
     * @param descGetter  描述Getter
     * @param selectEnums 下拉枚举项
     * @param <T>         枚举类型
     * @param <K>         code类型
     * @return 枚举项生成的下拉项列表
     */
    @SafeVarargs
    static <T extends Enum<T>, K> List<SelectVo> asList(Function<T, K> codeGetter, Function<T, String> descGetter, T... selectEnums) {
        return Arrays.stream(Objects.requireNonNull(selectEnums))
                .map(o -> new SelectVo(String.valueOf(codeGetter.apply(o)), descGetter.apply(o))).collect(Collectors.toList());
    }

    /**
     * 通过键查找SelectEnum
     * 方便非静态Class对象调用，无法强转为复合泛型Class
     *
     * @param code              下拉编码
     * @param selectEnumClass   下拉枚举类
     * @throws IllegalArgumentException 当枚举类不为SelectEnum的实现类时，会报错参数不合法异常
     * @see #getByCode(Object, Class)
     */
    static <K> SelectEnum<K> getByCodeGeneric(K code, Class<? extends Enum<?>> selectEnumClass) {
        if (SelectEnum.class.isAssignableFrom(selectEnumClass)) {
            return (SelectEnum<K>) CodeEnum.getByCodeGeneric(code, selectEnumClass);
        }
        throw new IllegalArgumentException("Class is not an implementation of SelectEnum :" + selectEnumClass);
    }

    /**
     * 将枚举项转换为下拉列表
     * 方便非静态Class对象调用，无法强转为复合泛型Class
     *
     * @param selectEnumClass   下拉枚举类名
     * @see #list(Class)
     * @throws IllegalArgumentException 当枚举类不为SelectEnum的实现类时，会报错参数不合法异常
     * @return 不要再返回的List上编辑
     */
    static List<SelectVo> listGeneric(Class<? extends Enum<?>> selectEnumClass) {
        if (SelectEnum.class.isAssignableFrom(selectEnumClass)) {
            return SelectEnumCache.getEnumSelectGeneric(selectEnumClass);
        }
        throw new IllegalArgumentException("Class is not an implementation of SelectEnum :" + selectEnumClass);
    }

    /**
     * 该类设计为非final是为了用户可以在SelectEnum实现中能够向下拉列表中新增额外属性。
     * 由于下拉列表使用了缓存，列表虽然不可以操作，但是其中的 SelectVo（子类对象）确可能发生改变。
     * 为了避免用户误操作修改了缓存中的 SelectVo 而污染缓存，它的子类应该设计成不可变的。
     */
    class SelectVo {
        private final String code;
        private final String desc;

        public SelectVo(String code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        public String getCode() {
            return code;
        }

        public String getDesc() {
            return desc;
        }

        @Override
        public String toString() {
            return "CodeDesc{" +
                    "code='" + code + '\'' +
                    ", desc='" + desc + '\'' +
                    '}';
        }
    }

    /**
     * 枚举下拉缓存，提高获取下拉框效率
     */
    class SelectEnumCache {

        private static final Map<Class<?>, List<SelectVo>> SELECT_CACHE = new ConcurrentReferenceHashMap<>();
        /**
         * 缓存开关，默认开启
         */
        private static final boolean USE_CACHE = Optional.ofNullable(System.getProperty("SELECT_ENUM_CACHE"))
                .map(val -> !"false".equalsIgnoreCase(val)).orElse(Boolean.TRUE);

        /**
         * 获取枚举下拉列表
         *
         * @param selectEnumClass   下拉枚举
         * @return  不要再返回的List上编辑
         */
        private static <T extends Enum<T> & SelectEnum<K>, K> List<SelectVo> getEnumSelect(Class<T> selectEnumClass) {
            if(USE_CACHE) {
                return SELECT_CACHE.computeIfAbsent(selectEnumClass,
                        k -> Collections.unmodifiableList(getEnumSelectInner(selectEnumClass)));
            }
            return getEnumSelectInner(selectEnumClass);
        }

        private static <T extends Enum<T> & SelectEnum<K>, K> List<SelectVo> getEnumSelectInner(Class<T> selectEnumClass) {
            return Arrays.<SelectEnum<K>>stream(selectEnumClass.getEnumConstants())
                    .map(SelectEnum::getSelectVo).filter(Objects::nonNull).collect(Collectors.toList());
        }

        /**
         * 获取枚举下拉列表
         * 方便非静态Class对象调用，无法强转为复合泛型Class
         *
         * @param selectEnumClass   必须是SelectEnum
         * @return  不要再返回的List上编辑
         */
        private static List<SelectVo> getEnumSelectGeneric(Class<? extends Enum<?>> selectEnumClass) {
            if(USE_CACHE) {
                return SELECT_CACHE.computeIfAbsent(selectEnumClass,
                        k -> Collections.unmodifiableList(getEnumSelectGenericInner(selectEnumClass)));
            }
            return getEnumSelectGenericInner(selectEnumClass);
        }

        private static List<SelectVo> getEnumSelectGenericInner(Class<? extends Enum<?>> selectEnumClass) {
            return Arrays.stream(selectEnumClass.getEnumConstants())
                    .map(o -> ((SelectEnum<?>) o).getSelectVo()).filter(Objects::nonNull).collect(Collectors.toList());
        }

    }

}
